package lion.se.lucene;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;

import lion.dev.lang.KV;
import lion.se.lucene.index.IDocumentBuilder;
import lion.se.lucene.index.IndexConf;
import lion.se.lucene.search.IQueryBuilder;
import lion.se.lucene.search.SearchResult;

/**
 * 
 * Lucene搜索引擎管理类,负责索引的创建，更新，合并，销毁,查询等操作。
 * 
 * @author lion
 * @email hl_0074@sina.com
 * @date 2014年4月14日
 */
public class SEManager {

	private Directory directory;
	private IndexReader indexReader;
	private IndexSearcher indexSearcher;
	private IndexWriter indexWriter;
	private Analyzer analyzer;
	private boolean batch = false;
	private boolean changed = false;
	private static Log log = LogFactory.getLog(SEManager.class);

	public SEManager() {

		try {
			init(null, new RAMDirectory());
		} catch (IOException e) {
		}
	}

	public SEManager(Analyzer analyzer) {

		try {
			init(analyzer, new RAMDirectory());
		} catch (IOException e) {
		}
	}

	public SEManager(String dir) throws IOException {

		this(dir, null);
	}

	public SEManager(String dir, Analyzer analyzer) throws IOException {

		init(analyzer, FSDirectory.open(Paths.get(dir)));
	}

	/**
	 * 销毁目录，删除所有文档
	 */
	public void destory() {

		if (this.indexWriter != null) {
			try {

				this.indexWriter.deleteAll();
				this.indexWriter.close();
			} catch (IOException e) {
				log.error("destory index error", e);
			}

		}
		this.indexWriter = null;
		this.indexReader = null;
		this.indexSearcher = null;
		changed = true;
	}

	public void index(IDocumentBuilder... builders) {

		try {
			for (IDocumentBuilder documentBuilder : builders) {
				this.indexWriter.addDocument(documentBuilder.build());
			}
			if (!batch) {
				this.indexWriter.commit();
				changed = true;
			}
		} catch (IOException e) {
			log.error("error on index document", e);
		}
	}

	public void index(List<IDocumentBuilder> builders) {

		this.index((IDocumentBuilder[]) builders.toArray());
	}

	public int count(IQueryBuilder queryBuilder) {

		Query query = queryBuilder.build(this.analyzer);

		TotalHitCountCollector docsCollector = new TotalHitCountCollector();
		try {
			this.getSearcher().search(query, docsCollector);
		} catch (IOException e) {
			log.error("error on count ", e);
			return 0;
		}
		return docsCollector.getTotalHits();
	}

	public SearchResult search(IQueryBuilder queryBuilder) throws IOException {

		int start = queryBuilder.getStart();
		int size = queryBuilder.getSize();

		if (start < 0) {
			start = 0;
		}
		if (size <= 0) {
			size = 10;
		}
		int numDoc = start + size;

		SearchResult searchResult = new SearchResult();
		Query query = queryBuilder.build(this.analyzer);

		long startTime = System.currentTimeMillis();

		TopDocs topDocs = null;
		Sort sort = queryBuilder.getSort();

		if (sort == null) {
			sort = new Sort();
			sort.setSort(SortField.FIELD_SCORE);
		}

		TopFieldCollector tfc = TopFieldCollector.create(sort, numDoc, false, true, true);
		IndexSearcher is = this.getSearcher();

		is.search(query, tfc);
		topDocs = tfc.topDocs(start, size);
		searchResult.setTotal(topDocs.totalHits);

		for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
			Document doc = is.doc(scoreDoc.doc);
			searchResult.getDocuments().add(doc);
		}

		searchResult.setTime(System.currentTimeMillis() - startTime);

		return searchResult;
	}

	public void delete(String fld, String value) {

		try {
			this.indexWriter.deleteDocuments(new Term(fld, value));
			if (!batch) {
				this.indexWriter.commit();
				changed = true;
			}
		} catch (IOException e) {
			log.error("error on delete document", e);
		}
	}

	public void delete(String[][] kvs) {

		for (String[] kv : kvs) {
			this.delete(kv[0], kv[1]);
		}
	}

	public void delete(Map<String, String> param) {

		for (String key : param.keySet()) {
			this.delete(key, param.get(key));
		}
	}

	public void udpate(String fld, IDocumentBuilder builder) {

		Document doc = builder.build();
		String val = doc.get(fld);
		try {
			this.indexWriter.updateDocument(new Term(fld, val), doc);
			if (!batch) {
				this.indexWriter.commit();
				changed = true;
			}
		} catch (IOException e) {
			log.error("error on update document ", e);
		}
	}

	@SafeVarargs
	public final void udpate(KV<String, ? extends IDocumentBuilder>... kvs) {

		for (KV<String, ? extends IDocumentBuilder> kv : kvs) {
			this.udpate(kv.getKey(), kv.getValue());
		}
	}

	/**
	 * 合并索引
	 */
	public void merge(SEManager other) {

		try {
			other.indexWriter.close();
			this.indexWriter.addIndexes(other.directory);
			this.optimize();
		} catch (IOException e) {
			log.debug("error on merge index", e);
		}
		changed = true;
	}

	/**
	 * 优化索引文件
	 */
	public void optimize() throws IOException {

		if (this.changed) {
			this.indexWriter.commit();
		}
		this.indexWriter.forceMergeDeletes(false);
		this.indexWriter.deleteUnusedFiles();
		this.changed = true;
	}

	public void setBatch(boolean batch) {

		this.batch = batch;
		if (!batch) {
			try {
				this.indexWriter.commit();
				changed = true;
			} catch (IOException e) {
				log.error("error on commit", e);
			}
		}
	}

	private void init(Analyzer analyzer, Directory directory) throws IOException {

		this.directory = directory;
		this.analyzer = analyzer != null ? analyzer : new StandardAnalyzer();
		this.indexWriter = new IndexWriter(this.directory, IndexConf.getConf(this.analyzer));
	}

	/**
	 * 获取搜索器，如果不是索引根目录，则加载所有索引目录
	 */
	private IndexSearcher getSearcher() throws IOException {

		if (this.indexReader == null || changed) {
			if (this.indexReader == null) {
				this.indexReader = DirectoryReader.open(this.directory);
			} else {
				this.indexReader = DirectoryReader.openIfChanged((DirectoryReader) this.indexReader);
			}
			this.indexSearcher = new IndexSearcher(this.indexReader);
			this.changed = false;
		}
		return this.indexSearcher;
	}
}
